import {TriggerOpTypes} from "./operation";

export function effect(fn, options = {}) {
  const effect = createReactiveEffect(fn, options);

  if(!options.lazy){
    effect(); // 默认就要执行
  }

  return effect;
}


// 创建响应式effect
let uid = 0;
let activeEffect;
const effectStack = []; // 栈结构

function createReactiveEffect(fn,options){
  const effect = function reactiveEffect() {
    if(!effectStack.includes(effect)/**←如果effect已经在栈中,就不需要在执行下面的逻辑了,防止effect(() => state.name = state.name++)这种死循环的情况*/){
      try {
        effectStack.push(effect);
        activeEffect = effect; // 将effect放到了activeEffect上
        return fn();

      }finally { //finally特性 即使上面return了 也会先执行finally里的
        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1];
      }
    }

  };

  effect.options = options;
  effect.id = uid++;
  effect.deps = []; // 依赖了哪些属性(属性变化了 就会执行该effect)

  return effect;
}


/*
//effect1
effect(()=>{
  state.a + state.b
})

//effect2
effect(()=>{
  state.a
})


//这样记录↓
//↓↓一个WeakMap
{
  //↓↓一个普通Map
  {a:1,b:1}:{
    a: Set { effect1 , effect2 } ,
    b: Set { effect1 }
  }
  ...
}

详情参看effect依赖存储结构
* */

const targetMap = new WeakMap(); /** ←← weakmap和map一样 但是是弱引用 不会导致内存泄漏*/
export function track(target, type, key) {
  if(activeEffect === undefined){ //←← 只有在effect()时 才track
    return;
  }

  let depsMap = targetMap.get(target);

  if(!depsMap){
    targetMap.set(target, (depsMap = new Map()));
  }

  let dep = depsMap.get(key);

  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  if (!dep.has(activeEffect)) {
    dep.add(activeEffect); // {"{name:'ahhh'}":{name: Set{ effect1 } }}
    activeEffect.deps.push(dep); // 双向记忆 让effectX也记录dep
  }
}

export function trigger(target, type, key, value, oldValue){
  const depsMap = targetMap.get(target); // 获取当前对应的map

  if(!depsMap){ //更改的属性是没有在effect()里进行依赖收集的
    return;
  }
  const run = (effects) => {
    if (effects) {
      effects.forEach(effect => effect());
    }
  };

  if (key !== null) {
    run(depsMap.get(key));
  }

  if (type === TriggerOpTypes.ADD) {
    // [1,2,3]
    // arr.push(4)
    // push时会触发三次get,分别是 arr、 push 和 length
    // 而length属性在模板渲染时,如果arr被使用到了,那么length就已经绑定了依赖(渲染模板时会对arr调用json.stringify,stringify调用时,内部嵌套的属性的访问器都会被访问一遍)
    // 故我们在这里可以找到length属性 调用它的依赖

    run(depsMap.get(Array.isArray(target) ? 'length' : ''));
  }
}
